#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ksw_llrf.py - 低电平射频（LLRF）控制系统核心模块

该模块实现了低电平射频控制系统的核心功能，包括：
1. 腔体控制
2. 状态机管理
3. 自动加载控制
4. 联锁保护
5. 日志记录
"""

from tools import read_conf, rs_afg, vacuum, arc, interlock, cavity, ssa, decorator
from epics import PV, caget, caput, cainfo, poll
from importlib import reload
import random
import numpy as np
import threading
import time
import datetime
import sys
import os
import logging
import json

# 配置文件路径
NEW_LLRF_CONF = '../conf/KSWLLRF1-1.conf'
# 迭代计数（约120秒）
ITER_COUNT = 1200
# 日志创建时间
CREATE_LOG_TIME = '00:01:00'

# 操作模式定义
MANUAL_MODE     = 0  # 手动模式
AUTO_MODE       = 1  # 自动模式
PHY_PHASE_SCAN  = 0x020c0  # 物理相位扫描
PHY_RUNNING     = 0x3f     # 物理运行

# 状态机状态定义
ST_OFF          = 0   # 关闭状态
ST_PRE_LOAD     = 1   # 预加载状态
ST_RAMP_LOAD    = 2   # 斜坡加载状态
ST_ADJ_LOAD     = 3   # 调整加载状态
ST_FINISHED     = 4   # 完成状态
ST_FAILED       = 5   # 失败状态
ST_IDLE         = 6   # 空闲状态
ST_RELD_CONF    = 7   # 重载配置状态
ST_FAIL_CONP    = 8   # 失败补偿状态
ST_INC_MANI     = 9   # 手动干预状态
ST_CM_LOAD      = 10  # CM加载状态
ST_INLK         = 11  # 联锁状态
ST_DOSE_LOAD    = 12  # 剂量加载状态
ST_CAV_BYP      = 13  # 腔体旁路状态

# 日志级别定义
DEBUG           = 0  # 调试级别
INFO            = 1  # 信息级别
WARNING         = 2  # 警告级别
ERROR           = 3  # 错误级别
CRITICAL        = 4  # 严重错误级别

# 逻辑值定义
ON              = 1  # 开启
OFF             = 0  # 关闭
ENABLED         = 1  # 使能
DISABLED        = 0  # 禁用
PREPARED        = 1  # 已准备
UNPREPARED      = 0  # 未准备
BUSY            = 1  # 忙碌
FREE            = 0  # 空闲
TRIGGERED       = 1  # 已触发
UNTRIGGERED     = 0  # 未触发

# 错误代码定义
SSA_OUT_INLK        = 110  # SSA输出联锁
SSA_SELF_INLK       = 120  # SSA自检联锁
SSA_INLK_INLK       = 130  # SSA联锁状态
CAV_VAC_INLK        = 321  # 腔体真空联锁
CPL_VAC_INLK        = 322  # 耦合器真空联锁
CAV_ARC_INLK        = 312  # 腔体电弧联锁
CPL_ARC_INLK        = 313  # 耦合器电弧联锁
LLRF_OFFLINE        = 211  # LLRF离线
LLRF_PHAS_OPEN      = 233  # LLRF相位开环
LLRF_OUT_OF_CTL     = 232  # LLRF失控
CAV_TEMP_INLK       = 331  # 腔体温度联锁
CPL_BIAS_INLK       = 334  # 耦合器偏置联锁
CM_INLK             = 410  # CM联锁
VALVE_INLK          = 413  # 阀门联锁
AUTO_LOAD_FAILED    = 510  # 自动加载失败
AMP_CHECK_FAILED    = 511  # 幅度检查失败
TUNE_TIMEOUT        = 512  # 调谐超时
RAMP_LOAD_FAILED    = 513  # 斜坡加载失败
EPK_TUNE_FAILED     = 514  # EPK调谐失败
FREQ_TUNE_FAILED    = 515  # 频率调谐失败
CLOSELOOP_FAILED    = 516  # 闭环失败
PRELOAD_FAILED      = 517  # 预加载失败
DOOR_INLK           = 611  # 门联锁

class SCCavity(object):
    """
    超导腔体控制类
    
    该类实现了对超导腔体的全面控制，包括：
    1. 腔体状态监控
    2. 自动加载控制
    3. 联锁保护
    4. 故障补偿
    5. 日志记录
    """
    
    def selfCheck(self):
        """
        自检函数
        
        功能：
        1. 检查腔体相关状态
        2. 检查RFPS系统状态
        3. 检查软联锁状态
        
        返回：
            bool: 自检是否通过
        """
        self.self_check_status = []
        self.print_log(INFO, self.cm_cav+' <-LLRF self check function->')
        self.log_data  = '\n  <-LLRF self check function->'
        self.log_data += '\n  ---Cavity relatived status---'
        self.log_data += '\n    '+self.cavity_vacuum.status
        if self.coupler_vacuum == None: pass
        else: self.log_data += '\n    '+self.coupler_vacuum.status
        self.log_data += '\n  ---RFPS system status---'
        self.log_data += '\n    '+self.cm_soft_interlock.status
        self.self_check_status.append(self.cm_soft_interlock.value)
        self.log_data += '\n    '+self.cavity_temp_softinlk.status
        self.self_check_status.append(self.cavity_temp_softinlk.value)
        self.log_data += '\n    '+self.coupler_external_temperature_soft_interlock.status
        self.self_check_status.append(self.coupler_external_temperature_soft_interlock.value)
        self.log_data += '\n    '+self.latched_cm_soft_interlock.status
        self.log_data += '\n    '+self.latched_cavity_temperature_soft_interlock.status
        self.log_data += '\n    '+self.latched_coupler_external_temperature_soft_interlock.status
        self.print_log(INFO, self.log_data)
        if self.is_alive == True:
            if False in self.self_check_status:
                self.print_log(INFO, self.log_data)
                self.auto_start.value = 0
                return False
            self.print_log(INFO, self.log_data)
            return True
        else:
            self.print_log(ERROR, self.log_data)
            return False

    def timeout(threadname, total_hours):
        global is_event_end, count_phase, is_event_fetch, is_flag_end
        print(threadname)
        count_phase = 0
        for i in range(total_hours*60*12):
            time.sleep(5)
            is_event_fetch.set()
        is_flag_end = True
        return 0

    def print_log(self, log_level, log_str):
        print(log_str)
        if log_level == DEBUG:
            logging.debug(log_str)
        elif log_level == INFO:
            logging.info(log_str)
        elif log_level == WARNING:
            logging.warning(log_str)
        elif log_level == ERROR:
            logging.error(log_str)
        elif log_level == CRITICAL:
            logging.critical(log_str)
        else:
            logging.debug(log_str)

    @property
    def send_error_code(self):
        pass

    @send_error_code.setter
    def send_error_code(self, error_msg):
        '''
        Type: 2DimList, numpy ndarray
        format: [[timestamp(double), cav_no(int), error_code(str)]]
        example:
        a=["['1632626815.852501', '1', '110']", "['1632626815.852501', '2', '111']", "['1632626815.852501', '11', '230']"]
        '''
        if self.error_code_list.value == '[]':
            error_code_list_temp = [[]]
        else:
            error_code_list_temp = self.error_code_list.value
        if len(error_code_list_temp) >= self.error_code_list.pv.nelm:
            np.delete(error_code_list_temp, self.error_code_list.pv.nelm-1)
        else:
            self.error_code_list.value = np.append(str(error_msg), error_code_list_temp)
            self.error_code_no.value = str(error_msg[2])

    @property
    def is_alive(self):
        self.print_log(DEBUG, self.cm_cav+' <-LLRF status check function->')
        self.print_log(INFO, self.cm_cav+' LLRF work status(FPGA, ARM): '+str(self.watchdog_fpga.pv.connected)+', '+str(self.watchdog_arm.pv.connected))
        if False in (self.watchdog_fpga.pv.connected, self.watchdog_arm.pv.connected):
            self.print_log(ERROR, self.cm_cav+' LLRF not work well yet, please check LLRF chassis status')
            self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, LLRF_OFFLINE]
            return False
        else:
            self.print_log(INFO, self.cm_cav+' LLRF work normally')
            return True

    def detuning(self):
        self.print_log(DEBUG, self.cm_cav+' <-Failure conpensation detuning function->')
        self.print_log(WARNING, 'Detuning mode for failure conpensation')
        self.quit_auto_loading
        self.tuner_speed.value = self.detuning_speed
        self.print_log(INFO, self.cm_cav+' Detuning speed: '+str(self.tuner_speed.value))
        self.frequency_upward.on
        self.print_log(DEBUG, self.cm_cav+' Detuning cavity_frequency sets to move up')
        time.sleep(3)
        self.frequency_upward.off
        self.print_log(WARNING, 'Detuning cavity_frequency sets to move up done, last 3 seconds')
        self.tuner_speed.value = self.tuner_speed_old
        return True

    @property
    def quit_auto_loading(self):
        self.print_log(DEBUG, self.cm_cav+' <-Quit autoloding function->')
        self.cavity_rf_ready.unprepared    # All finished not ready
        self.amplitude_closeloop_ready.unprepared 
        self.phase_closeloop_ready.unprepared 
        self.frequency_closeloop_ready.unprepared
        self.target_amplitude_ready.unprepared
        self.target_phase_ready.unprepared
        self.target_frequency_ready.unprepared
        self.print_log(DEBUG, self.cm_cav+' All finished status switched to unprepared: '+self.cavity_rf_ready.status)
        self.rf_on.off    # Turn off RF
        time.sleep(0.2)
        self.auto_start.off    # Emergency stop on, reset auto_start button
        self.print_log(DEBUG, self.cm_cav+' Turn off RF: '+self.rf_on.status)
        self.frequency_downward.off    # Stop cavity_frequency move up
        self.print_log(DEBUG, self.cm_cav+' Stop cavity_frequency move up: '+self.frequency_downward.status)
        self.frequency_upward.off    # Stop cavity_frequency move down
        self.print_log(DEBUG, self.cm_cav+' Stop cavity_frequency move down: '+self.frequency_upward.status)
        self.auto_tuning.disabled    # Stop auto cavity_frequency tunning
        self.print_log(DEBUG, self.cm_cav+' Stop auto cavity_frequency tunning: '+self.auto_tuning.status)
        self.state = ST_IDLE    # To idle state
        self.bypass_interlock.disabled
        self.emergency_stop.on
        self.dose_measure.off
        self.print_log(WARNING, 'Quit autoloading process: All finish status switched to unprepared, Turn off RF, Stop cavity_frequency move up or move down, Stop auto cavity_frequency tunning, To reload configure state.')
        return True

    @property
    def reset_interlocks(self):
        self.print_log(DEBUG, self.cm_cav+' <-Reset error status function->')
        # Double
        self.interlock_reset.enabled    # Reset set to 1
        time.sleep(0.2)
        self.interlock_reset.disabled    # Reset set to 0
        time.sleep(0.5)
        self.interlock_reset.enabled    # Reset set to 1
        time.sleep(0.2)
        self.interlock_reset.disabled    # Reset set to 0
        self.print_log(INFO, self.cm_cav+' Reset interlock to LLRF: Arc, Quench, etc.')
        return True

    @property
    def amp_readback(self):
        self.print_log(DEBUG, self.cm_cav+' <-Cavity amplitude readback check function->')
        self.amplitude_cw_drive.value = self.openloop_init
        self.print_log(DEBUG, self.cm_cav+' Openloop drive initial value set: '+self.amplitude_cw_drive.status)
        self.amplitude_setpoint.value = self.closeloop_init    # According to the amplitude_error to set, TODO
        self.print_log(DEBUG, self.cm_cav+' Closeloop drive initial value set: '+self.amplitude_setpoint.status)
        self.sel_loop_on.disabled     # GDR mode on
        time.sleep(0.2)
        self.print_log(DEBUG, self.cm_cav+' GDR mode on(0): '+self.sel_loop_on.status)
        self.print_log(INFO,'GDR mode on for amplitude readback.')
        if self.emergence_stop == True: return False
        self.sel_loop_on.enabled    # SEL mode on
        time.sleep(0.2)
        self.print_log(DEBUG, self.cm_cav+' SEL mode on(1): '+self.sel_loop_on.status)
        time.sleep(1)
        self.print_log(DEBUG, self.cm_cav+' SEL mode on, cavity amptitude Epk readback: '+str(self.epk_current.value))
        if self.emergence_stop == True: return False
        if self.epk_current.value >= self.cavity_amplitude_epk_margin:
            self.print_log(INFO, self.cm_cav+' Cavity amplitude Epk readback: '+str(self.epk_current.value)+', which greater than cavity amplitude Epk readback margin: '+str(self.cavity_amplitude_epk_margin)+', RF power check successfully while switched to SEL mode.')
            return True
        else:
            self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, AMP_CHECK_FAILED]
            self.state = ST_FAILED
            self.print_log(ERROR, self.cm_cav+' Cavity amplitude Epk readback: '+str(self.epk_current.value)+', which less than cavity amplitude Epk readback margin: '+str(self.cavity_amplitude_epk_margin)+', RF power check fail while switched to SEL mode.')
            self.quit_auto_loading
            return False

    @property
    def emergence_stop(self):
        if self.state == ST_OFF:
            self.print_log(DEBUG, self.cm_cav+' <-Emergence stop function->')
            self.quit_auto_loading
            return True
        else: return False

    def tunningCounter(self, func):
        if self.emergence_stop == True: return False
        else:
            time.sleep(0.1)
            self.itr_cnt -= 1
            if self.itr_cnt == 0:
                self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, TUNE_TIMEOUT]
                self.state = ST_FAILED
                self.print_log(ERROR, self.cm_cav+' Time out at '+func+' after '+str(0.1*ITER_COUNT)+' seconds , quit autoloading procedure')
                self.quit_auto_loading
                return False
            else: return True

    @property
    def loading_epk_to_target(self):
        self.print_log(DEBUG, self.cm_cav+' <-Epk to target setting loading function->')
        if self.mode == 1:
            self.phase_setpoint.value = self.phase_target.value
            while abs(self.phase_setpoint.value - self.phase_target.value) > 0.1:
                time.sleep(1)
            self.print_log(DEBUG, self.cm_cav+' Phase setpoint setting to target value after closeloop: '+self.phase_setpoint.pvname+' = '+str(self.phase_setpoint.value))
            time.sleep(0.1)
            self.target_phase_ready.prepared
        self.epk_target_value = self.epk_target.value
        self.epk_current_value = self.epk_current.value
        
        if self.state == ST_RAMP_LOAD or self.state == ST_ADJ_LOAD:    # Auto on state
            while self.epk_current.value < self.epk_target.value:
                if self.epk_current.value < self.epk_target.value - 2.0:
                    self.amplitude_setpoint.value += self.amplitude_setpoint_step
                else: self.amplitude_setpoint.value += 5
                time.sleep(self.epk_step_wait)
                self.itr_cnt = ITER_COUNT
                if self.state == ST_RAMP_LOAD or self.state == ST_ADJ_LOAD:
                    while abs(self.tuning_error.value) >= self.tuning_error_margin and self.itr_cnt > 0:
                        if self.tunningCounter('loading_epk_to_target1') == True: continue
                        else: break
                else:
                    self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, EPK_TUNE_FAILED]
                    return False
            while self.epk_current.value > self.epk_target.value:
                if self.epk_current.value > self.epk_target.value + 2.0:
                    self.amplitude_setpoint.value -= self.amplitude_setpoint_step
                else:
                    self.amplitude_setpoint.value -= 5
                time.sleep(self.epk_step_wait)
                self.itr_cnt = ITER_COUNT
                if self.state == ST_RAMP_LOAD or self.state == ST_ADJ_LOAD:
                    while abs(self.tuning_error.value) >= self.tuning_error_margin and self.itr_cnt > 0:
                        if self.tunningCounter('loading_epk_to_target3') == True: continue
                        else: break
                else:
                    self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, EPK_TUNE_FAILED]
                    return False
        self.amplitude_setpoint_current = self.amplitude_setpoint.value
        self.target_amplitude_ready.prepared
        self.print_log(DEBUG, self.cm_cav+' Amplitude setpoint sets to target value finally: '+self.amplitude_setpoint.status+', and update current setpoint register.')
        self.print_log(INFO, self.cm_cav+' ready loading to Epk: '+str(self.epk_target.value)+' MV/m.')
        self.auto_start.off    # Release the auto start button
        self.print_log(DEBUG, self.cm_cav+' Release the auto start button(0): '+self.auto_start.status)
        self.cavity_rf_ready.prepared    # All finish state
        self.state = ST_FINISHED
        self.print_log(INFO, self.cm_cav+' Amplitude setpoint sets to target value finally, release the auto start button, put all finish state to 1.')
        if self.mode == 2:
            self.dose_loading_record
        return True

    @property
    def frequency_check(self):
        self.print_log(DEBUG, self.cm_cav+' <-cavity_frequency check and tuning function->')
        self.frequency_error = self.cavity_frequency.value*10**6 - 162500000
        if self.frequency_error >= self.frequency_error_down_margin and self.frequency_error <= self.frequency_error_up_margin: return True
        else:
            if self.frequency_error > self.frequency_error_up_margin:
                self.frequency_downward.on    # cavity_frequency move down
                self.print_log(DEBUG, self.cm_cav+' Current cavity_frequency: '+str(self.cavity_frequency.value*10**6)+', Start cavity_frequency move down...')
                self.itr_cnt = ITER_COUNT
                while self.frequency_error > self.frequency_error_up_margin  and self.itr_cnt > 0:
                    if self.tuner_speed_enable == True:
                        self.tuner_speed_temp = abs(self.frequency_error / self.tuning_speed_kp)
                        #print('Frequency moving down, current tuner speed:', self.tuner_speed_temp)
                        if self.tuner_speed_temp >= self.tuner_speed_max:
                            self.tuner_speed.value = self.tuner_speed_max
                        elif self.tuner_speed_temp <= self.tuner_speed_old:
                            self.tuner_speed.value = self.tuner_speed_old
                        else:
                            self.tuner_speed.value = self.tuner_speed_temp
                    else:
                        pass
                    if self.tunningCounter('frequency_check1') == True:
                        self.frequency_error = self.cavity_frequency.value*10**6 - 162500000
                    else: 
                        self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, FREQ_TUNE_FAILED]
                        return False
                    #print('Frequency error:', self.frequency_error)
                self.frequency_downward.off    # Stop cavity_frequency move down
                self.print_log(DEBUG, self.cm_cav+' Cavity tuned, stop cavity_frequency move down(0): '+self.frequency_upward.status)
                return True
            elif self.frequency_error < self.frequency_error_down_margin:
                self.frequency_upward.on    # cavity_frequency move up
                self.print_log(DEBUG, self.cm_cav+' Current cavity_frequency: '+str(self.cavity_frequency.value*10**6)+', Start cavity_frequency move up...')
                self.itr_cnt = ITER_COUNT
                while self.frequency_error < self.frequency_error_down_margin and self.itr_cnt > 0:
                    if self.tuner_speed_enable == True:
                        self.tuner_speed_temp = abs(self.frequency_error / self.tuning_speed_kp)
                        #print('Frequency monving up, current tuner speed:', self.tuner_speed_temp)
                        if self.tuner_speed_temp >= self.tuner_speed_max:
                            self.tuner_speed.value = self.tuner_speed_max
                        elif self.tuner_speed_temp <= self.tuner_speed_old:
                            self.tuner_speed.value = self.tuner_speed_old
                        else:
                            self.tuner_speed.value = self.tuner_speed_temp
                    else:
                        pass
                    if self.tunningCounter('frequency_check2') == True:
                        self.frequency_error = self.cavity_frequency.value*10**6 - 162500000
                    else: 
                        self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, FREQ_TUNE_FAILED]
                        return False
                    #print(self.frequency_error)
                self.frequency_upward.off    # Stop cavity_frequency move up
                self.print_log(DEBUG, self.cm_cav+' Cavity tuned, stop cavity_frequency move down(0): '+self.frequency_downward.status)
                return True

    @property
    def start_to_closeloop(self):
        self.print_log(DEBUG, self.cm_cav+' <-Start up to pre-closeloop setpoint function->')
        self.sel_loop_on.off    # GDR mode on
        time.sleep(0.2)
        self.print_log(DEBUG, self.cm_cav+' GDR mode on(0): '+self.sel_loop_on.status)
        if abs(self.tuning_error.value) <= 100:
            self.auto_tuning.enabled    # Tuner auto tuning
            self.frequency_closeloop_ready.prepared
            self.target_frequency_ready.prepared
            self.print_log(DEBUG, self.cm_cav+' Tuner auto tuning on(1): '+self.auto_tuning.status)
        else:
            self.print_log(ERROR, self.cm_cav+' AutoTuning fail, tunner error larger than 50: '+self.tuning_error.status)
            self.state = ST_FAILED
            self.quit_auto_loading
            return False
        self.itr_cnt = ITER_COUNT
        while abs(self.tuning_error.value) >= 15 and self.itr_cnt > 0:
            if self.tunningCounter('start_to_closeloop1') == True: continue
            else:
                self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, FREQ_TUNE_FAILED]
                return False
        #self.closeloop_init = self.cavity_amplitude.value
        self.print_log(DEBUG, self.cm_cav+' Amplitude closed loop value changed to: '+str(self.closeloop_init))
        self.amplitude_setpoint.value = self.closeloop_init    # According to the amplitude_error to set, TODO
        self.amplitude_closeloop.enabled
        self.amplitude_closeloop_ready.prepared
        self.print_log(DEBUG, self.cm_cav+' Amplitude closed loop(1): '+self.amplitude_closeloop.status)
        self.phase_setpoint.value = 0
        while abs(self.phase_setpoint.value - 0) > 0.1:
            time.sleep(1)
        self.print_log(DEBUG, self.cm_cav+' Phase closed setppoint sets to 0: '+self.phase_setpoint.status)
        self.itr_cnt = ITER_COUNT
        while abs(self.tuning_error.value) >= self.tuning_error_margin or abs(self.phase_error.value) >= 10 and self.itr_cnt > 0: # If phase err always larger than 15, phase offset adjust required.
            if self.tunningCounter('start_to_closeloop2') == True: continue
            else:
                self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, FREQ_TUNE_FAILED]
                return False
        self.phase_closeloop.enabled
        self.phase_closeloop_ready.prepared
        self.print_log(DEBUG, self.cm_cav+' Phase closed loop(1): '+self.phase_closeloop.status)
        self.print_log(INFO, self.cm_cav+' auto on done, cavity_frequency, amplitude and phase are all closed loop.')
        time.sleep(0.5)
        if self.phase_closeloop.value == ENABLED and self.amplitude_closeloop.value == ENABLED and self.auto_tuning.value == ENABLED:
            self.state = ST_RAMP_LOAD  # To Epk/Phase target state
            return True
        else:
            self.print_log(ERROR, self.cm_cav+' Auto on start_to_closeloop fail, cavity_frequency, amplitude and phase not all closed loop.')
            self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, CLOSELOOP_FAILED]
            return False

    @property
    def auto_preloading(self):
        self.print_log(DEBUG, self.cm_cav+' <-Preloading main function->')
        start_time = datetime.datetime.now()
        self.print_log(INFO, self.cm_cav+' Preloading process start at: '+start_time.strftime('%Y-%m-%d_%H:%M:%S.%f'))
        if self.is_alive == True:
            self.reset_interlocks
            self.rf_on.on    # Turn on RF
            self.print_log(DEBUG, self.cm_cav+' LLRF RF on switched to on(1): '+self.rf_on.status)
            if self.amp_readback == True and self.frequency_check == True and self.start_to_closeloop == True:
                stop_time = datetime.datetime.now()
                self.print_log(INFO, self.cm_cav+' Preloading process stop at: '+stop_time.strftime('%Y-%m-%d_%H:%M:%S.%f'))
                self.print_log(INFO, self.cm_cav+' Preloading process last: '+str(stop_time-start_time))
                self.print_log(INFO, self.cm_cav+' Preloading process done.')
                self.state = ST_RAMP_LOAD    # To Epk/Phase tuning state
                return True
            else:
                self.send_error_code = [datetime.datetime.now().timestamp(), self.cav_code, PRELOAD_FAILED]
                return False
        else:
            self.state = ST_FAILED    # Fail
            stop_time = datetime.datetime.now()
            self.print_log(INFO, self.cm_cav+' Preloading process failed at: '+stop_time.strftime('%Y-%m-%d_%H:%M:%S.%f'))
            return False

    @property
    def dose_preloading(self):
        self.dose_file_path = dose_conf(self.cm_cav)
        dose_file_title = 'Epk(MV/m), Gamma1(uSv/h), Gamma2(uSv/h), Gamma3(uSv/h)'
        read_conf.writeFile(self.dose_file_path, dose_file_title)
        self.print_log(DEBUG, self.cm_cav+' <- Dose preloading main function->')
        start_time = datetime.datetime.now()
        if self.is_alive == True:
            self.reset_interlocks
            self.rf_on.on    # Turn on RF
            self.print_log(DEBUG, self.cm_cav+' LLRF RF on switched to on(1): '+self.rf_on.status)
            if self.amp_readback == True and self.frequency_check == True and self.start_to_closeloop == True:
                stop_time = datetime.datetime.now()
                self.print_log(INFO, self.cm_cav+' Dose preloading process:'+' start at '+start_time.strftime('%Y-%m-%d_%H:%M:%S.%f')+' stop at '+stop_time.strftime('%Y-%m-%d_%H:%M:%S.%f')+', last '+str(stop_time-start_time))
                self.phase_closeloop.disabled
                self.phase_closeloop_ready.unprepared
                self.auto_tuning.disabled    # Tuner auto tuning off
                self.frequency_closeloop_ready.unprepared
                self.sel_loop_on.on    # SEL mode on
                self.print_log(INFO, self.cm_cav+' Dose preloading process done, amplitude loop closed, phase and frequency loop open, switch to SEL mode.')
                self.state = ST_RAMP_LOAD    # To ramping load state
                self.dose_epk_target = self.dose_epk1_setpoint
                self.dose_duration = self.dose_epk1_duration
                self.epk_target.value = self.dose_epk_target
                return True
            else: return False
        else:
            self.state = ST_FAILED    # Fail
            stop_time = datetime.datetime.now()
            self.print_log(INFO, self.cm_cav+' Dose preloading process failed at: '+stop_time.strftime('%Y-%m-%d_%H:%M:%S.%f'))
            return False

    @property
    def dose_loading_record(self):
        while self.dose_duration >= 0:
            if self.epk_current.value - self.dose_epk_target <= self.dose_quench_margin:
                dose_info = 'Quench occured at Epk: {} MV/m'
                self.print_log(INFO, dose_info.format(self.dose_epk_target))
                self.state = ST_OFF
                break
            if self.state == ST_OFF:
                break
            else:
                time.sleep(0.5)
                self.dose_duration -= 0.5
        dose_info = '{}, {}, {}, {}'
        read_conf.writeFile(self.dose_file_path, dose_info.format(self.dose_epk_target, self.dose_gamma1.value, self.dose_gamma2.value, self.dose_gamma3.value))

    @property
    def dose_loading(self):
        # Tuning ramploading value
        if self.dose_epk_target < self.dose_epk2_setpoint:
            self.dose_epk_target += self.dose_epk1_step
            self.dose_duration = self.dose_epk1_duration
            self.epk_target.value = self.dose_epk_target
            self.state = ST_ADJ_LOAD
        elif self.dose_epk_target < self.dose_epk3_setpoint:
            self.dose_epk_target += self.dose_epk2_step
            self.dose_duration = self.dose_epk2_duration
            self.epk_target.value = self.dose_epk_target
            self.state = ST_ADJ_LOAD
        elif self.dose_epk_target < self.dose_epk4_setpoint:
            self.dose_epk_target += self.dose_epk3_step
            self.dose_duration = self.dose_epk3_duration
            self.epk_target.value = self.dose_epk_target
            self.state = ST_ADJ_LOAD
        else:
            self.print_log(INFO, self.cm_cav+' finished dose measurement.')
            self.state = ST_OFF
        epk_info = 'Epk tuning to {} MV/m'
        self.print_log(INFO, epk_info.format(self.epk_target.value))

    def on_valve_enable_change(self, valve_enable=None):
        if self.init_status == True:
            if valve_enable.value == 0:
                self.state = ST_INLK
                self.send_error_code = [valve_enable.timestamp, 49, VALVE_INLK]
            else:
                self.state_previous = ST_IDLE
                self.state = ST_IDLE
        else: pass

    def on_door_interlock_change(self, door_interlock=None):
        if self.init_status == True:
            if door_interlock.value == 0:
                if self.door_interlock_status == True: pass
                else:
                    self.door_interlock_status = True
                    self.send_error_code = [door_interlock.timestamp, 99, DOOR_INLK]
                    self.state_previous = ST_OFF
                    self.state = ST_OFF
            else:
                self.door_interlock_status = False
        else: pass

    def on_cavity_bypassed_change(self, cavity_bypassed=None):
        if self.init_status == True:
            self.print_log(INFO, self.cm_cav+' cavity bypass changed, current operation mode: '+str(cavity_bypassed.value)+', '+str(self.operation_mode.value))
            if int(self.operation_mode.value) == PHY_PHASE_SCAN or int(self.operation_mode.value) == PHY_RUNNING:
                self.state = ST_CAV_BYP
            else: pass
        else: pass

    def on_phase_target_change(self, value=None):
        if self.init_status == True:
            if self.operation_mode.value == PHY_PHASE_SCAN:
                if self.phase_closeloop.value == ON:
                    self.state = ST_RAMP_LOAD
                else:
                    self.state = ST_CM_LOAD
            else: pass
        else: pass

    def on_failure_conpensate_change(self, value=None):
        if self.init_status == True:
            pass #self.state = ST_FAIL_CONP
        else: pass

    def on_failure_conpensate_no_change(self, value=None):
        if self.init_status == True:
            pass #self.state = ST_FAIL_CONP
        else: pass

    def on_epk_target_change(self, epk=None):
        if self.init_status == True:
            print('Epk_target status:', epk.value)
            print('Operation_mode:', self.operation_mode.value)
            if self.operation_mode.value == PHY_PHASE_SCAN or self.operation_mode.value == PHY_RUNNING:
                if epk.value == 0: self.state = ST_OFF
                else:
                    self.print_log(INFO, self.cm_cav+' cavity_rf_ready status: '+str(self.cavity_rf_ready.value))
                    if self.cavity_rf_ready.value == PREPARED:
                        self.state = ST_RAMP_LOAD
                    elif self.mode == 1:
                        self.state = ST_CM_LOAD
                    elif self.mode == 2:
                        self.state = ST_RAMP_LOAD
            else: pass
        else: pass

    def on_epk_current_change(self, epk_current=None):
        if self.init_status == True:
            pass
        else: pass

    def on_rf_on_change(self, rf_on=None):
        if self.init_status == True:
            if rf_on.value == OFF:
                self.print_log(INFO, self.cm_cav+' rf_on status changed: '+str(self.rf_on.value))
                if self.operation_mode.value == PHY_RUNNING:
                    self.send_error_code = [rf_on.timestamp, self.cav_code, LLRF_OUT_OF_CTL]
                self.state = ST_OFF
            else: pass
        else: pass

    def on_phase_closeloop_change(self, phase_closeloop=None):
        if self.init_status == True:
            if phase_closeloop.value == 0 and self.auto_start.value == 1:
                self.print_log(INFO, self.cm_cav+' phase_closeloop status changed: '+str(self.phase_closeloop.value))
                self.send_error_code = [phase_closeloop.timestamp, self.cav_code, LLRF_PHAS_OPEN]
            else: pass
        else: pass
        
    def on_auto_function_change(self, auto=None):
        if self.init_status == True:
            if auto.value == LOADING:
                self.print_log(DEBUG, self.cm_cav+' Auto conditioning function')
                self.state = ST_IDLE # To stop state
                self.state_previous = ST_IDLE
            if auto.value == CONDITION:
                self.print_log(DEBUG, self.cm_cav+' Auto start function')
                self.state = ST_IDLE # To stop state
                self.state_previous = ST_IDLE
        else: pass

    def on_cm_auto_start_change(self, cm_auto_start=None):
        if self.init_status == True:
            if self.state == ST_IDLE:
                self.state_previous = ST_IDLE
                if cm_auto_start.value == ON: self.state = ST_CM_LOAD
                else: self.state = ST_OFF
            else: pass
        else: pass
    
    def on_cmall_auto_start_change(self, cmall_auto_start=None):
        if self.init_status == True:
            if self.state == ST_IDLE:
                self.state_previous = ST_IDLE
                if cmall_auto_start.value == ON: 
                    if self.cm_auto_start.value == OFF:
                        self.cm_auto_start.pv.value = ON
                    self.state = ST_CM_LOAD
                else:
                    if self.cm_auto_start.value == ON:
                        self.cm_auto_start.pv.value = OFF
                    self.state = ST_OFF
            else: pass
        else: pass

    def on_emergency_stop_change(self, stop=None):
        if self.init_status == True:
            if stop.value == ON:
                self.state = ST_OFF # To stop state
                self.state_previous = ST_IDLE
                if self.emergence_stop == True:
                    self.print_log(DEBUG, self.cm_cav+' Current status: '+str(self.state)+self.state_log)
            else: pass
        else: pass

    def on_dose_measure_change(self, dose=None):
        if self.init_status == True:
            if dose.value == ON:
                self.mode = 2
                self.print_log(INFO, self.cm_cav+' switch to dose measure mode')
                self.state = ST_PRE_LOAD
            elif dose.value == OFF:
                self.mode = 1
                self.print_log(INFO, self.cm_cav+' switch to normal mode')
        else: pass

    def on_auto_start_change(self, start=None):
        if self.init_status == True:
            if start.value == ON:
                if self.emergency_stop.value == OFF:
                    if self.state_previous in self.state_recoverable and self.state == ST_IDLE:
                        self.state = self.state_previous
                    elif self.door_interlock.value == OFF:
                        self.state = ST_INLK
                    else:
                        self.state_previous = ST_PRE_LOAD
                        self.state = ST_PRE_LOAD    # To auto start state
                else:
                    self.state = ST_INC_MANI    # Emergency stop on, reset auto_start button
            else:
                if self.emergency_stop.value == OFF:
                    if self.state in self.state_recoverable:
                        self.state_previous = self.state
                        self.state = ST_IDLE
        else: pass

    def init_values(self):
        self.state_dict ={0:'off', 1:'preloading', 2:'ramp loading', 3:'adjusting loading', 4:'loading finished', 5:'loading failed', 6:'idle', 7:'load configuration', 8:'failure conpensation', 9:'Incorrect manipulation', 10:'cm auto load', 11:'door interlock', 12:'dose load', 13:'cavity bypass'}
        self.state = ST_IDLE
        self.state_previous = ST_IDLE
        self.state_recoverable = [1, 2, 3]
        self.llrf_version = self.pv_dict['llrf_version'][1]
        self.cavity = self.pv_dict['cavity'][1]
        self.cm_cav = self.pv_dict['cm_cav'][1]
        self.cm_cav_short = self.pv_dict['cm_cav_short'][1]
        self.cav_no = int(self.pv_dict['cav_no'])
        self.cav_code = int(self.pv_dict['cav_code'])
        print('load_default_value status: ', self.pv_dict['load_default_value'][1])
        if self.pv_dict['load_default_value'][1] == 'True': self.load_default_value = True
        else: self.load_default_value = False
        self.auto_start_bt = 0 # 0 for button on
        self.emergency_stop_bt = 0 # 0 for button on
        self.detuning_speed = 4000 # Failure conpenstation detuning speed
        self.tuner_speed_max = float(self.pv_dict['tuner_speed_max'])
        self.tuning_speed_kp = float(self.pv_dict['tuning_speed_kp'])
        self.epk_target_value = 0
        self.epk_current_value = 0
        self.point_step_small = 10
        self.frequency_error_up_margin = float(self.pv_dict['frequency_error_up_margin'])
        self.frequency_error_down_margin = float(self.pv_dict['frequency_error_down_margin'])
        self.tuning_error_margin = float(self.pv_dict['tuning_error_margin'])
        self.state = ST_IDLE
        self.state_log = '(0:off, 1:auto on to pre-setpoint, 2:epk/phase tuning after 1, 3:epk/phase tuning after all ready, 4:done, 5:fail, 6:idle, 7:reload configure file, 8:failure conpensation, 9:incorrect manipulation, 10:cm auto load, 11:door interlock, 12:dose load, 13:cavity bypass)'
        self.itr_cnt = 0
        self.iteration_timeout = float(self.pv_dict['iteration_timeout'])
        self.epk_step_wait = float(self.pv_dict['epk_step_wait'])
        self.closeloop_init = float(self.pv_dict['closeloop_init'])
        self.openloop_init = float(self.pv_dict['openloop_init'])
        self.cavity_amplitude_margin = float(self.pv_dict['cavity_amplitude_margin'])
        self.cavity_amplitude_epk_margin = float(self.pv_dict['cavity_amplitude_epk_margin'])
        self.amplitude_setpoint_step = int(self.pv_dict['amplitude_setpoint_step'])
        self.door_interlock_status = None
        ITER_COUNT = int(self.iteration_timeout / self.epk_step_wait)
        self.conf_file = self.pv_dict['conf_file']

    def on_phy_calibrated_epk_change(self, phy_calibrated_epk=None):
        if self.init_status == True:
            phy_epk = json.loads(phy_calibrated_epk.value)
            if self.cm_cav_short in phy_epk:
                print(self.cm_cav_short, phy_epk[self.cm_cav_short])
                if abs(phy_calibrated_epk.value - self.epk_current.value) > self.epk_calibrated_margin:
                    pass
                else:
                    pass
                    # Qe need to be updated
                    # temp = self.cavity_Qe.value
                    # k = phy_calibrated_epk.value / self.epk_current.value
                    # self.cavity_Qe.value = k ** 2 * temp
                    # self.print_log(INFO, self.cm_cav+' Pickup Qe changed (%s -> %s)' % (temp, self.cavity_Qe.value))
        else: pass

    def on_phy_finish_change(self, phy_finish=None):
        if self.init_status == True:
            pass
        else: pass

    def init_control(self):
        # PVs for Control
        if 'failure_conpensate' in self.pv_dict:
            self.failure_conpensate = decorator.readOnly(self.pv_dict['failure_conpensate'], callback=self.on_failure_conpensate_change)
        if 'failure_conpensate_no' in self.pv_dict:
            self.failure_conpensate_no = decorator.readOnly(self.pv_dict['failure_conpensate_no'], callback=self.on_failure_conpensate_no_change)
        self.phase_target = decorator.readOnly(self.pv_dict['phase_target'], callback=self.on_phase_target_change)
        self.epk_target = decorator.readWrite(self.pv_dict['epk_target'], callback=self.on_epk_target_change)
        self.auto_start = decorator.readWrite(self.pv_dict['auto_start'], callback=self.on_auto_start_change)
        self.emergency_stop = decorator.readWrite(self.pv_dict['emergency_stop'], callback=self.on_emergency_stop_change)
        self.dose_measure = decorator.readWrite(self.pv_dict['dose_measure'], callback=self.on_dose_measure_change)
        self.epk_current = decorator.readOnly(self.pv_dict['epk_current'], callback=self.on_epk_current_change)
        self.rf_ready = decorator.readWrite(self.pv_dict['rf_ready'])
        self.amplitude_closeloop_ready = decorator.readWrite(self.pv_dict['amplitude_closeloop_ready'])
        self.phase_closeloop_ready = decorator.readWrite(self.pv_dict['phase_closeloop_ready'])
        self.target_amplitude_ready = decorator.readWrite(self.pv_dict['target_amplitude_ready'])
        self.target_phase_ready = decorator.readWrite(self.pv_dict['target_phase_ready'])
        self.target_frequency_ready = decorator.readWrite(self.pv_dict['target_frequency_ready'])
        self.frequency_closeloop_ready = decorator.readWrite(self.pv_dict['frequency_closeloop_ready'])
        self.cavity_rf_ready = decorator.readWrite(self.pv_dict['cavity_rf_ready'])
        self.bypass_interlock = decorator.readWrite(self.pv_dict['bypass_interlock'])
        self.ampplitude_out_limiter = decorator.readWrite(self.pv_dict['ampplitude_out_limiter'])
        self.ampplitude_out_limiter.value = float(self.pv_dict['ampplitude_out_limiter_init'])
        self.phy_calibrated_epk = decorator.readWrite(self.pv_dict['phy_calibrated_epk'], callback=self.on_phy_calibrated_epk_change)
        self.phy_finish = decorator.readWrite(self.pv_dict['phy_finish'], callback=self.on_phy_finish_change)
        self.operation_mode = decorator.readOnly(self.pv_dict['operation_mode'])
        self.cavity_bypassed = decorator.readOnly(self.pv_dict['cavity_bypassed'], callback=self.on_cavity_bypassed_change)
        self.door_interlock = decorator.readOnly(self.pv_dict['door_interlock'], callback=self.on_door_interlock_change)
        self.cm_auto_start = decorator.readOnly(self.pv_dict['cm_auto_start'], callback=self.on_cm_auto_start_change)
        self.cmall_auto_start = decorator.readOnly(self.pv_dict['cmall_auto_start'], callback=self.on_cmall_auto_start_change)
        if 'valve_enable' in self.pv_dict:
            self.valve_enable = decorator.readOnly(self.pv_dict['valve_enable'], callback=self.on_valve_enable_change)
        self.cavity_Qe = decorator.readWrite(self.pv_dict['cavity_Qe'])

    def init_closeloop(self):
        # PVs for close loop
        self.cavity_frequency = decorator.readOnly(self.pv_dict['cavity_frequency'])
        self.rf_on = decorator.readWrite(self.pv_dict['rf_on'], callback=self.on_rf_on_change)
        self.sel_loop_on = decorator.readWrite(self.pv_dict['sel_loop_on'])
        self.amplitude_cw_drive = decorator.readWrite(self.pv_dict['amplitude_cw_drive'])
        self.amplitude_setpoint = decorator.readWrite(self.pv_dict['amplitude_setpoint'])
        self.amplitude_setpoint_current = self.amplitude_setpoint.value
        self.phase_setpoint = decorator.readWrite(self.pv_dict['phase_setpoint'])
        self.amplitude_error = decorator.readWrite(self.pv_dict['amplitude_error'])
        self.phase_error = decorator.readWrite(self.pv_dict['phase_error'])
        self.amplitude_loopstate = decorator.readWrite(self.pv_dict['amplitude_loopstate'])
        self.phase_loopstate = decorator.readWrite(self.pv_dict['phase_loopstate'])
        self.amplitude_closeloop = decorator.readWrite(self.pv_dict['amplitude_closeloop'])
        self.phase_closeloop = decorator.readWrite(self.pv_dict['phase_closeloop'], callback=self.on_phase_closeloop_change)

    def init_monitor(self):
        # PVs for monitor
        self.watchdog_fpga = decorator.readOnly(self.pv_dict['watchdog_fpga'])
        self.watchdog_arm = decorator.readOnly(self.pv_dict['watchdog_arm'])
        self.cavity_amplitude = decorator.readOnly(self.pv_dict['cavity_amplitude'])
        self.cavity_phase = decorator.readOnly(self.pv_dict['cavity_phase'])
        self.ssa_forward_amplitude = decorator.readOnly(self.pv_dict['ssa_forward_amplitude'])
        self.ssa_forward_phase = decorator.readOnly(self.pv_dict['ssa_forward_phase'])
        self.cavity_vacuum = decorator.readOnly(self.pv_dict['cavity_vacuum'])
        if 'coupler_vacuum' in self.pv_dict:
            self.coupler_vacuum = decorator.readOnly(self.pv_dict['coupler_vacuum'])
        else: self.coupler_vacuum = None
        self.wait_idle_indicator = decorator.readWrite(self.pv_dict['wait_idle_indicator'])
        self.loading_indicator = decorator.readWrite(self.pv_dict['loading_indicator'])
        self.dose_gamma1 = decorator.readWrite(self.pv_dict['dose_gamma1'])
        self.dose_gamma2 = decorator.readWrite(self.pv_dict['dose_gamma2'])
        self.dose_gamma3 = decorator.readWrite(self.pv_dict['dose_gamma3'])
        self.error_code_list = decorator.readWrite(self.pv_dict['error_code_list'])
        self.error_code_no = decorator.readWrite(self.pv_dict['error_code_no'])
        if 'ssa_reverse_amplitude' in self.pv_dict:
            self.ssa_reverse_amplitude = decorator.readOnly(self.pv_dict['ssa_reverse_amplitude'])
        if 'ssa_reverse_phase' in self.pv_dict:
            self.ssa_reverse_phase = decorator.readOnly(self.pv_dict['ssa_reverse_phase'])
        
        # PVs for tuner control
        if 'tuner_speed' in self.pv_dict:
            self.tuner_speed = decorator.readWrite(self.pv_dict['tuner_speed'])
            self.tuner_speed_old = self.tuner_speed.value
            self.tuner_speed_enable = True
        else:
            self.tuner_speed_enable = False
        self.frequency_upward = decorator.readWrite(self.pv_dict['frequency_upward'])
        self.frequency_downward = decorator.readWrite(self.pv_dict['frequency_downward'])
        if 'tuner_position' in self.pv_dict:
            self.tuner_position = decorator.readOnly(self.pv_dict['tuner_position'])
        self.auto_tuning = decorator.readWrite(self.pv_dict['auto_tuning'])
        self.tuning_error = decorator.readOnly(self.pv_dict['tuning_error'])
        if 'tuner_uplimiter' in self.pv_dict:
            self.tuner_uplimiter = decorator.readOnly(self.pv_dict['tuner_uplimiter'])
        if 'tuner_downlimiter' in self.pv_dict:
            self.tuner_downlimiter = decorator.readOnly(self.pv_dict['tuner_downlimiter'])

    def init_interlock(self):
        # PVs for interlock
        self.led_di = decorator.readOnly(self.pv_dict['led_di'])
        self.led_quench = decorator.readOnly(self.pv_dict['led_quench'])
        self.led_interlock = decorator.readOnly(self.pv_dict['led_interlock'])
        self.interlock_reset = decorator.readWrite(self.pv_dict['interlock_reset'])

    def on_cavity_vacuum_interlock_change(self, vacuum_inlk):
        if self.init_status == True:
            if self.bypass_cavity_vacuum_interlock.value == ON: pass
            else:
                if vacuum_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' cavity_vacuum_interlock status changed: '+str(vacuum_inlk.value))
                    self.send_error_code = [vacuum_inlk.timestamp, self.cav_code, CAV_VAC_INLK]
        else: pass

    def on_coupler_vacuum_interlock_change(self, vacuum_inlk):
        if self.init_status == True:
            if self.bypass_coupler_vacuum_interlock.value == ON: pass
            else:
                if vacuum_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' coupler_vacuum_interlock status changed: '+str(vacuum_inlk.value))
                    self.send_error_code = [vacuum_inlk.timestamp, self.cav_code, CPL_VAC_INLK]
        else: pass

    def on_cavity_arc_interlock_change(self, arc_inlk):
        if self.init_status == True:
            if self.bypass_cavity_arc_interlock.value == ON: pass
            else:
                if arc_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' cavity_arc_interlock status changed: '+str(arc_inlk.value))
                    self.send_error_code = [arc_inlk.timestamp, self.cav_code, CAV_ARC_INLK]
        else: pass

    def on_coupler_arc_interlock_change(self, arc_inlk):
        if self.init_status == True:
            if self.bypass_coupler_arc_interlock.value == ON: pass
            else:
                if arc_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' coupler_arc_interlock status changed: '+str(arc_inlk.value))
                    self.send_error_code = [arc_inlk.timestamp, self.cav_code, CPL_ARC_INLK]
        else: pass

    def on_llrf_interlock_change(self, llrf_inlk):
        if self.init_status == True:
            pass
        else: pass

    def on_cavity_temperature_softinlk_change(self, cavity_temp_inlk):
        if self.init_status == True:
            if self.bypass_cavity_temperature_softinlk.value == ON: pass
            else:
                if cavity_temp_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' cavity_temp_interlock status changed: '+str(cavity_temp_inlk.value))
                    self.send_error_code = [cavity_temp_inlk.timestamp, self.cav_code, CAV_TEMP_INLK]
        else: pass

    def on_coupler_external_temperature_soft_interlock_change(self, coupler_temp_ext_inlk):
        if self.init_status == True:
            if self.bypass_coupler_external_temperature_soft_interlock.value == ON: pass
            else:
                if coupler_temp_ext_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' coupler_external_temperature_interlock status changed: '+str(coupler_temp_ext_inlk.value))
                    self.send_error_code = [coupler_temp_ext_inlk.timestamp, self.cav_code, CPL_BIAS_INLK]
        else: pass

    def on_cm_soft_interlock_change(self, cm_inlk):
        if self.init_status == True:
            if cm_inlk.value == ON: pass
            else:
                if cm_inlk.value == 0:
                    self.print_log(INFO, self.cm_cav+' cm_soft_interlock status changed: '+str(cm_inlk.value))
                    self.send_error_code = [cm_inlk.timestamp, self.cav_code, CM_INLK]
                    self.state = ST_OFF
        else: pass

    def init_rfps_system(self):
        self.cavity_vacuum_interlock = decorator.readOnly(self.pv_dict['cavity_vacuum_interlock'], callback=self.on_cavity_vacuum_interlock_change)
        self.latched_cavity_vacuum_interlock = decorator.readOnly(self.pv_dict['latched_cavity_vacuum_interlock'])
        self.bypass_cavity_vacuum_interlock = decorator.readOnly(self.pv_dict['bypass_cavity_vacuum_interlock'])

        self.coupler_vacuum_interlock = decorator.readOnly(self.pv_dict['coupler_vacuum_interlock'], callback=self.on_coupler_vacuum_interlock_change)
        self.latched_coupler_vacuum_interlock = decorator.readOnly(self.pv_dict['latched_coupler_vacuum_interlock'])
        self.bypass_coupler_vacuum_interlock = decorator.readOnly(self.pv_dict['bypass_coupler_vacuum_interlock'])

        self.cavity_arc_interlock = decorator.readOnly(self.pv_dict['cavity_arc_interlock'], callback=self.on_cavity_arc_interlock_change)
        self.latched_cavity_arc_interlock = decorator.readOnly(self.pv_dict['latched_cavity_arc_interlock'])
        self.bypass_cavity_arc_interlock = decorator.readOnly(self.pv_dict['bypass_cavity_arc_interlock'])

        self.coupler_arc_interlock = decorator.readOnly(self.pv_dict['coupler_arc_interlock'], callback=self.on_coupler_arc_interlock_change)
        self.latched_coupler_arc_interlock = decorator.readOnly(self.pv_dict['latched_coupler_arc_interlock'])
        self.bypass_coupler_arc_interlock = decorator.readOnly(self.pv_dict['bypass_coupler_arc_interlock'])

        self.llrf_interlock = decorator.readOnly(self.pv_dict['llrf_interlock'], callback=self.on_llrf_interlock_change)

        self.cavity_temperature_softinlk = decorator.readOnly(self.pv_dict['cavity_temperature_softinlk'], callback=self.on_cavity_temperature_softinlk_change)
        self.latched_cavity_temperature_soft_interlock = decorator.readOnly(self.pv_dict['latched_cavity_temperature_soft_interlock'])
        self.bypass_cavity_temperature_softinlk = decorator.readOnly(self.pv_dict['bypass_cavity_temperature_softinlk'])

        self.coupler_external_temperature_soft_interlock = decorator.readOnly(self.pv_dict['coupler_external_temperature_soft_interlock'], callback=self.on_coupler_external_temperature_soft_interlock_change)
        self.latched_coupler_external_temperature_soft_interlock = decorator.readOnly(self.pv_dict['latched_coupler_external_temperature_soft_interlock'])
        self.bypass_coupler_external_temperature_soft_interlock = decorator.readOnly(self.pv_dict['bypass_coupler_external_temperature_soft_interlock'])

        self.cm_soft_interlock = decorator.readOnly(self.pv_dict['cm_soft_interlock'], callback=self.on_cm_soft_interlock_change)
        self.latched_cm_soft_interlock = decorator.readOnly(self.pv_dict['latched_cm_soft_interlock'])
        self.bypass_cm_soft_interlock = decorator.readOnly(self.pv_dict['bypass_cm_soft_interlock'])

        self.fps_interlock_reset = decorator.readWrite(self.pv_dict['fps_interlock_reset'])
        self.bypass_llrf_interlock = decorator.readWrite(self.pv_dict['bypass_llrf_interlock'])

    def __init__(self, pv_dict, **kwargs):
        #print(pv_dict)
        self.init_status = False
        self.pv_dict = pv_dict
        self.init_values()
        self.init_control()
        self.init_closeloop()
        self.init_monitor()
        self.init_interlock()
        self.init_rfps_system()
        self.init_status = True
        print('Init finished.')

def logging_conf(cm_cav):
    log_date_dir = 'logs/'+datetime.datetime.now().strftime('%Y-%m-%d')
    log_file = log_date_dir+'/'+cm_cav+'.log'
    time.sleep(random.random()*5)
    reload(logging)
    logging.basicConfig(filename=log_file, level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
    return log_date_dir

def load_configure(conf_file):
    pv_dict = read_conf.readPVDict(conf_file)
    pv_dict['conf_file'] = conf_file
    log_date_dir = logging_conf(pv_dict['cm_cav'][1])
    llrf = SCCavity(pv_dict)
    llrf.bdr_data_file = log_date_dir+'/'+llrf.cavity+'_BDR_data.csv'
    llrf.bdr_statistical_data_file = log_date_dir+'/'+llrf.cavity+'_BDR_statistical_data.csv'
    return llrf
        
def reload_configure(llrf):
    pv_dict = read_conf.readPVDict(llrf.conf_file)
    pv_dict['conf_file'] = llrf.conf_file
    log_date_dir = logging_conf(pv_dict['cm_cav'][1])
    llrf.pv_dict = pv_dict
    llrf.init_values()

def dose_conf(cav_no):
    dose_path = 'dose'
    if os.path.isdir(dose_path): pass
    else:
        os.mkdir(dose_path)
    dose_dir = 'dose/'+datetime.datetime.now().strftime('%Y-%m-%d')
    if os.path.isdir(dose_dir): pass
    else:
        os.mkdir(dose_dir)
    dose_file_path = dose_dir+'/'+cav_no+'_'+datetime.datetime.now().strftime('%H-%M-%S')+'.csv'
    return dose_file_path

def manual_mode_loading(llrf):
    llrf.state = ST_IDLE

def auto_mode_loading(llrf):
    if llrf.init_status == True:
        if llrf.state == ST_OFF:
            llrf.quit_auto_loading
        elif llrf.state == ST_PRE_LOAD:
            if llrf.operation_mode.value == PHY_RUNNING and llrf.cavity_bypassed.value == 1:
                llrf.state = ST_OFF
            else:
                reload_configure(llrf)
                llrf.auto_preloading
        elif llrf.state == ST_RAMP_LOAD:
            llrf.loading_epk_to_target
        elif llrf.state == ST_ADJ_LOAD:
            llrf.loading_epk_to_target
        elif llrf.state == ST_FINISHED:
            llrf.state = ST_IDLE
        elif llrf.state == ST_FAILED:
            llrf.send_error_code = [datetime.datetime.now().timestamp(), llrf.cav_code, AUTO_LOAD_FAILED]
            llrf.state = ST_IDLE
        elif llrf.state == ST_IDLE:
            if llrf.operation_mode.value == PHY_RUNNING:
                if llrf.cavity_bypassed.value == 1: pass
                else:
                    if llrf.cm_auto_start.value == 1 and (llrf.state_previous == ST_INLK or llrf.epk_current.value <= 1):
                        time.sleep(10)
                        llrf.state = ST_CM_LOAD
            time.sleep(0.2)
        elif llrf.state == ST_FAIL_CONP:    # Failure compensate mode
            llrf.detuning()
            llrf.state = ST_OFF
        elif llrf.state == ST_INC_MANI:
            llrf.auto_start.off    # Emergency stop on, reset auto_start button
            llrf.state = ST_IDLE
        elif llrf.state == ST_CM_LOAD:
            llrf.emergency_stop.off
            time.sleep(0.2)
            llrf.auto_start.on 
        elif llrf.state == ST_INLK:
            if llrf.state_previous != ST_INLK:
                llrf.state_previous = ST_INLK
                llrf.quit_auto_loading
            else: time.sleep(0.2)
        elif llrf.state == ST_CAV_BYP:
            if llrf.cavity_bypassed.value == 1:
                if llrf.bypass_llrf_interlock.value == 0:
                    llrf.bypass_llrf_interlock.value = 1
                llrf.state = ST_OFF
            else:
                if llrf.bypass_llrf_interlock.value == 1:
                    llrf.bypass_llrf_interlock.value = 0
                llrf.state = ST_IDLE
        if llrf.state == llrf.state_temp:
            if datetime.datetime.now().strftime("%H:%M:%S") == CREATE_LOG_TIME:
                log_date_dir = logging_conf(llrf.cm_cav)
                llrf.bdr_data_file = log_date_dir+'/'+llrf.cavity+'_BDR_data.csv'
                llrf.bdr_statistical_data_file = log_date_dir+'/'+llrf.cavity+'_BDR_statistical_data.csv'
                llrf.print_log('debug', 'Recreated log files at '+datetime.datetime.now().strftime("%H:%M:%S"))
                time.sleep(2)
            else:
                pass
        else:
            llrf.print_log('debug', 'Previous state: '+str(llrf.state_dict[llrf.state_previous]))
            llrf.print_log('debug', 'Current state: '+str(llrf.state_dict[llrf.state]))
            llrf.state_temp = llrf.state
        if llrf.state != ST_IDLE:
            if llrf.wait_idle_indicator.value == FREE: pass
            else: llrf.wait_idle_indicator.free
    else:
        llrf.state_previous = ST_IDLE
        llrf.state = ST_OFF

def dose_mode_loading(llrf):
    if llrf.state == ST_OFF: 
        llrf.quit_auto_loading
    elif llrf.state == ST_PRE_LOAD:
        reload_configure(llrf) 
        llrf.dose_preloading
    elif llrf.state == ST_RAMP_LOAD:
        llrf.loading_epk_to_target
    elif llrf.state == ST_ADJ_LOAD:
        llrf.loading_epk_to_target
    elif llrf.state == ST_FINISHED:
        llrf.dose_loading
    elif llrf.state == ST_FAILED:
        llrf.state = ST_IDLE
    elif llrf.state == ST_IDLE:
        time.sleep(0.2)
    elif llrf.state == ST_INC_MANI:
        llrf.auto_start.off    # Emergency stop on, reset auto_start button
        llrf.state = ST_IDLE
    elif llrf.state == ST_INLK:    # LLRF protect or other reason to stop
        llrf.state = ST_IDLE

def sc_state_machine(llrf):
    llrf.state_temp = 0
    llrf.mode = 1
    while True:
        if llrf.mode == 1:
            auto_mode_loading(llrf)
        elif llrf.mode == 2:
            dose_mode_loading(llrf)

def llrf_main(llrf):
    while True:
        if llrf.mode == MANUAL_MODE:
            manual_mode_loading(llrf)
        elif llrf.mode == AUTO_MODE:
            auto_mode_loading(llrf)
        elif datetime.datetime.now().strftime("%H:%M:%S") == "00:00:00":
            logging_conf(pv_dict['cm_cav'][1])
        else: pass

def main():
    cmd_argv = str(sys.argv)
    if (len(sys.argv) != 2):
        print('input paramters error', cmd_argv)
        sys.exit(-1)
    else:
        conf_file = sys.argv[1]
    pv_dict = read_conf.readPVDict(conf_file)
    logging_conf(pv_dict['cm_cav'][1])
    llrf = SCCavity(pv_dict)
    llrf_main(llrf)

if __name__ == '__main__':
    main()
